package net.avcompris.binding.impl;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.avcompris.binding.BindFunctions;

import com.avcompris.common.annotation.Nullable;

final class XPathFunctionImpl<T> implements XPathFunction {

	public XPathFunctionImpl(
			@Nullable final T node,
			final String xpathFunctionName,
			final Method method,
			final BindFunctions fns) {

		this.node = node;
		this.fns = nonNullArgument(fns, "functions");
		this.xpathFunctionName = nonNullArgument(xpathFunctionName,
				"xpathFunctionName");
		this.method = nonNullArgument(method, "method");

		methodName = method.getName();

		paramTypes = method.getParameterTypes();

		paramCount = (paramTypes == null) ? 0 : paramTypes.length;
	}

	@Override
	public boolean matchesArity(final int arity) {

		return arity + 1 == paramCount; // First parameter is currentNode
	}

	private final T node;
	private final BindFunctions fns;
	private final String xpathFunctionName;
	private final Method method;
	private final String methodName;

	@Nullable
	private final Class<?>[] paramTypes;

	private final int paramCount;

	@Override
	public Object evaluate(@Nullable final Object[] args) {

		final Object[] params = new Object[paramCount];

		params[0] = node;

		for (int i = 1; i < paramCount; ++i) {

			params[i] = args[i - 1]; // First parameter is currentNode
		}

		final Object result;

		method.setAccessible(true);
		
		try {

			result = method.invoke(fns, params);

		} catch (final IllegalAccessException e) {
			throw new RuntimeException(
					"XPath function: " + xpathFunctionName + "(" + paramCount
							+ "), Java method: " + methodName + "()", e);
		} catch (final InvocationTargetException e) {
			throw new RuntimeException(
					"XPath function: " + xpathFunctionName + "(" + paramCount
							+ "), Java method: " + methodName + "()", e);
		}

		return result;
	}
}
